package com.csii.excelapp.util;

import com.csii.excelapp.entity.BackupCell;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hssf.util.CellReference;
import org.apache.poi.hssf.util.HSSFColor;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.math.BigDecimal;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 说明: ExcelUtil 用于打开一个 xls 或 xlsx 文件，使用 parseVarsFromExcelTemplate() 方法来解析
 * 一个Excel模板文件中的参变量，使用 fillVars2ExcelInstance()根据模板生成一个新的 excel
 * 文件并填充参变量到该表，
 * excel 规范模板文件后缀名：xlt(excel 97-2003) xltx (2003以后) xltm(带宏的模板文件)
 * 原拟仅允许使用 xlt xltx xltm 作为模板的限定后缀，但 officeOnlineApps 不支持显示这类文件，故改为
 * 以常规 xls xlsx 作为模板文件，因 officeOnlineApps 不支持宏和矢量图层，故模板文件中不能带有宏和矢量图层
 */
public class ExcelUtil {

    private final Logger LOG = LoggerFactory.getLogger(ExcelUtil.class);

    //Excel 打开后的内存对象
    private Workbook workbook = null;

    public List<String> getVarsDefPos() {
        return varsDefPos;
    }

    public void setVarsDefPos(List<String> varsDefPos) {
        this.varsDefPos = varsDefPos;
    }

    //从模板 excel 中获取的所有待替换的参变量的位置
    private List<String> varsDefPos = null;
    // 从模板 excel 中获取的所有定义的参变量，形如 $x=select * from xx;
    private Map<String, String> varsDefMap = null;
    //参变量名留存，用于生成完成后按该列表清除残留的参变量//TODO

    //用于保存特殊批注的列表 Map<String,List<String>>   key=需要跟随的参变量,List该参变量的多个属性
    private Map<String, String> commentMap = new HashMap<>();

    //用于保存需要将该行移动到最后的 excel 行的所有数据
    public ExcelUtil(String excelFile) throws Exception {
        workbook = openExcel(excelFile);
        parseVarsFromExcelTemplate(workbook);
    }

    private Map<String, Object> needMove2VarsFilledEnd = new HashMap<>();


    /**
     * 打开一个 excel 文件，返回该 excel 的内存对象 workbook
     *
     * @param excelPath excel模板文件路径
     * @return excel 的内存对象
     */
    private Workbook openExcel(String excelPath) throws Exception {
        // 获取Excel后缀名
        String fileType = excelPath.substring(excelPath.lastIndexOf(".") + 1);
        // 获取Excel文件
        File excelFile = new File(excelPath);

        if (!excelFile.exists()) {
            LOG.warn("指定的Excel文件不存在！");
            throw new Exception("指定的Excel文件不存在");
        }

        if ("xls".equalsIgnoreCase(fileType)) {
            try (FileInputStream inputStream = new FileInputStream(excelFile)) {
                workbook = new HSSFWorkbook(inputStream);
            } catch (Exception e) {
                throw new Exception("打开模板文件时发生未知错误");
            }
        } else if ("xlsx".equalsIgnoreCase(fileType)) {
            try (FileInputStream inputStream = new FileInputStream(excelFile)) {
                workbook = new XSSFWorkbook(inputStream);
            } catch (Exception e) {
                throw new Exception("打开模板文件时发生未知错误");
            }
        } else {
            LOG.warn(String.format("%s后缀的文件，不被认为是合法的 excel 文件", fileType));
            throw new Exception(fileType + " 后缀的文件，不被认为是合法的 excel 文件");
        }
        return workbook;
    }

    /**
     * 打开一个 excel 文件，返回该 excel 的内存对象 workbook
     *
     * @param excelPath excel模板文件路径
     * @return excel 的内存对象
     */
    public static Workbook openOtherExcel(String excelPath) throws Exception {

        Workbook workbook;
        // 获取Excel后缀名
        String fileType = excelPath.substring(excelPath.lastIndexOf(".") + 1);
        // 获取Excel文件
        File excelFile = new File(excelPath);

        if (!excelFile.exists()) {
            throw new Exception("指定的Excel文件不存在");
        }

        if ("xls".equalsIgnoreCase(fileType)) {
            try (FileInputStream inputStream = new FileInputStream(excelFile)) {
                workbook = new HSSFWorkbook(inputStream);
            }
        } else if ("xlsx".equalsIgnoreCase(fileType)) {
            try (FileInputStream inputStream = new FileInputStream(excelFile)) {
                workbook = new XSSFWorkbook(inputStream);
            }
        } else {
            throw new Exception(fileType + " 后缀的文件，不被认为是合法的 excel 文件");
        }
        return workbook;
    }
    /**
     * 关闭一个已经打开的 excel 文件的内存对象 workbook
     * poi 4.xx 以后需要使用 workbook.close()来关闭
     *
     * @param workbookObj excel打开后的内存对象
     * @return 关闭是否成功
     */
    // 保留，新版本需要
    // public boolean closeExcel(Workbook workbook) {
    //     try {
    //         if (null != workbook) {
    //             //workbook.close();
    //         }
    //     } catch (Exception e) {
    //         LOG.warn("关闭数据流出错！错误信息：" + e.getMessage());
    //         return false;
    //     }
    //     return true;
    // }

    /**
     * 解析一个已经打开的 excel 模板文件中的参变量的位置及其所有定义
     *
     * @param workbook excel 文件的内存对象
     * @return 返回一个存储有所有已定义的参变量的 map 集合,
     * list[0] 是每一个待填充参变量在excel文档中的 xy 坐标
     * list[1] 是每一个已经定义的参变量的解析式子
     */
    private Map<String, String> parseVarsFromExcelTemplate(Workbook workbook) throws Exception {
        Map<String, String> varsMap = new HashMap<>();
        varsDefPos = new ArrayList<>();
        varsDefMap = varsMap;

        for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
            Sheet sheetN = workbook.getSheetAt(i);
            for (int j = 0; j < sheetN.getLastRowNum() + 1; j++) {
                Row row = sheetN.getRow(j);
                if (row == null) {
                    continue;
                }
                for (int k = 0; k < row.getLastCellNum() + 1; k++) {
                    Cell cell = row.getCell(k);
                    if (cell == null) {
                        continue;
                    }
                    String varsName = getCellStringValue(cell);
                    Pattern pattern = Pattern.compile("[$#]");

                    if (varsName != null && !"".equals(varsName) && (varsName.contains("$") || varsName.contains("#"))) {
                        String[] split = varsName.split("=+");
                        //取 = 号前的参变量名，并判断是否有多个$号，如果有多个，则表示不合法
                        String varName = split[0];
                        Matcher matcher = pattern.matcher(varName);
                        int count = 0;
                        while (matcher.find()) {
                            count++;
                        }
                        //如果参变量由 $ 打头，且含有 = 号，表示是参变量的定义,则存入到返回 list 的 0 号索引位
                        if ((varsName.startsWith("$") || varsName.startsWith("#")) && varsName.contains("=") && count > 1) {
                            //这表示语句定义有误，形如: $xx$ss=select * from xxx;  直接忽略该条语句，程序继续
                            continue;
                        } else if ((varsName.startsWith("$") || varsName.startsWith("#")) && varsName.contains("=")) {
                            //拆解参变量表达式 $varName=select * from tb_table;  多个连续等号按一个计
                            String[] splitVars = varsName.split("=+");
                            if (splitVars.length < 2) {
                                // throw new Exception("参变量 " + splitVars[0] + " 的格式有误，请仔细检查");
                                continue;
                            } else if (splitVars.length == 2) {
                                varsMap.put(i + "@" + splitVars[0], splitVars[1]);
                            } else if (splitVars.length > 2) {
                                for (int l = 1; l < splitVars.length - 1; l++) {
                                    splitVars[1] += "=" + splitVars[l + 1];
                                }
                                varsMap.put(i + "@" + splitVars[0], splitVars[1]);
                            }

                        } else if ((varsName.contains("$") || varsName.contains("#") && varsName.matches("((?!=).)*"))) {
                            //一个单元格里包含多个参变量的处理
                            //如果该单元格里只有一个参变量定义
                            matcher = pattern.matcher(varsName);
                            count = 0;
                            while (matcher.find()) {
                                count++;
                            }
                            if ((varsName.startsWith("$") || varsName.startsWith("#")) && count == 1) {
                                //参变量如果由 $ 打头，但不含有 = 号，则表示待替换填充的参变量，存入到返回 list 的 1 号索引位
                                varsDefPos.add(i + "@" + varsName + "=" + j + "," + k);
                            }
                            //如果参变量不由 $ 打头，但该单元格的值中含有 $ 或 #，则不管其中包含有多少个参变量，均单独处理
                            else if (varsName.contains("$") || varsName.contains("#")) {
                                //单独处理 $S 和 $T 在同一单元格的情况
                                if (varsName.contains("$S") && varsName.contains("$T")) {
                                    //从参变量定义区搜索带有 $S 的全变量名，如 $Sxxx,$S23
                                    Set<String> keySet = varsDefMap.keySet();
                                    boolean beEqual = false;
                                    String varNameStr = "";
                                    for (String keyStr : keySet) {
                                        varNameStr = keyStr.split("@")[1];
                                        if (varName.contains(varNameStr)) {
                                            beEqual = true;
                                        }
                                    }
                                    if (beEqual) {
                                        varsDefPos.add(i + "@" + varNameStr + "$T" + "=" + j + "," + k);
                                    } else {
                                        continue;
                                    }
                                } else {
                                    varsDefPos.add(i + "@" + "_multi_" + "=" + j + "," + k);
                                }
                            }
                        } else {
                            continue;
                        }
                    }

                    /*
                       处理该单元格是否包含有专用于描述“自动统计后将指定‘总计’行”放到最后的说明
                       需求说明：Excel 报表的自动填充，有一种情况，带 $S = sql 语句的自动填充，
                       特别是不知道这个sql执行完成后到底有多少行，但需要在该语句的最后添加一行统计行，
                       且该专用的统计/小计/合计行已经设计于某一行中时，需要在具体数据填充到 Excel 报表的
                       最后，完成后将该专用的统计/小计/合计行移动到最后。


                       此处专门解析这个特定的批注，批注的行首带有两个$$
                       且必须要有几个要求：
                           1.必须要有         跟随参变量:$Sx        表示该值跟随某个参变量
                           2.必须要有         挪动到最后的区域:A1E1       表示将哪一个区域的行复制到最后
                           //(不实现)3.是否修复样式     修复样式参考:A3E3     如果需要移动到最后行/列的区域有样式，那么移动完成之后的样式需要修复，这个修复的样式参考到

                     */
                    Comment cellComment = cell.getCellComment();
                    if (cellComment != null) {
                        String commentStr = cellComment.getString().getString();
                        String[] splitStr = commentStr.split("\n");

                        // 是否有带有标头 $$
                        boolean beHeader = false;
                        // 是否写有跟随哪个参变量
                        boolean beFollowVariable = false;
                        // 是否修复样式
                        //boolean beRepairStyle = false;
                        //跟随参变量
                        String followVar$S = "";
                        //挪动到最后的区域
                        String followRegion = "";
                        //判断批注是否是带有 $$ 开头的特殊批注
                        for (int l = 0; l < splitStr.length; l++) {
                            if (!"".equals(splitStr[l]) && splitStr[l].startsWith("$$")) {
                                beHeader = true;
                                continue;
                            }
                            if (splitStr[l].contains("跟随参变量") && splitStr[l].split("[:：]")[1] != null && splitStr[l].split("[:：]")[1].contains("$S")) {
                                beFollowVariable = true;
                                followVar$S = splitStr[l].split("[:：]")[1].trim();
                                continue;
                            }
                            // 未作参数合法性校验
                            if (splitStr[l].contains("挪动到最后的区域") && splitStr[l].split("[:：]")[1] != null && !"".equals(splitStr[l].split("[:：]")[1])) {
                                followRegion = splitStr[l].split("[:：]")[1];
                                continue;
                            }
                        }

                        // 如果是带有 $$ 的特殊批注，则将满足必须要求的特殊批注写入到批注列表中
                        if (beHeader && beFollowVariable) {
                            if (followVar$S != null && followVar$S.contains("$S")) {
                                int length$S = followVar$S.split("$S").length;
                                if (length$S > 1) {
                                    throw new Exception("专用批注中的配置项----跟随参变量的配置，不能有多个带有 $S 的参变量");
                                }
                            }
                            followVar$S = followVar$S.trim();
                            commentMap.put(i + "@" + followVar$S, followRegion);
                        }

                        //处理自动不定长填充后，在最后添加统计行(或统计列)
                        /*
                         TODO Excel 坐标合法性判断，基本格式为 字母+数字（AB123)
                         允许以下情况
                            单个字母，表示一列，例如:A，表示 Excel 表中的 A 的这一列，只能有一个存在
                            单个数字：表示一行，例如 1，表示 Excel 表中序号为 1 的这一行，只能有一个存在
                            字母和数字，表示一个区域，例如 B3F3,表示从B3开始到F3结束的这一个区域，有且只能有一对
                            单个单元格，仅表示一个单元格，例如 E32，表示单元格 E32,这种情况暂不处理。
                         */
                        String regex = "([A-Z]+\\d+|[A-Z]+|[0-9]+)+";
                        String regex1or2 = "([A-Z]+|[0-9]+)";
                        String regex3 = "([A-Z]+\\d+)";
                        regex = "([A-Z]+\\d+){2}";  //临时修改为仅匹配第三种情况
                        Pattern compile1or2 = Pattern.compile(regex1or2);
                        Pattern compile3 = Pattern.compile(regex3);

                        Set<String> commentMapKeySet = commentMap.keySet();
                        for (String key : commentMapKeySet) {
                            String value = commentMap.get(key);

                            //////参数合法性判断开始
                            //第一种情况和第二种情况的配置参数合法性判断
                            //String rowOrColumn = "";
                            //if (Pattern.matches(regex, value)) {
                            //    Matcher matcher = compile1or2.matcher(value == null ? "" : value.trim());
                            //    int matcher_start = 0;
                            //    int total = 0;
                            //    while (matcher.find(matcher_start)) {
                            //        rowOrColumn = matcher.group(1);
                            //        System.out.println(matcher.group(1));
                            //        matcher_start = matcher.end();
                            //        total++;
                            //    }
                            //    //通过抛异常的方式向前端报出错误，如果配置参数有误的话
                            //    if (total != 1) {
                            //        //将该条记录标记为：失效
                            //        commentMap.put(key, "error");
                            //        throw new Exception("\"专用配置批注\"中的\"区域配置\": " + value + "不正确");
                            //    } else {
                            //        //修复别的地方可能对该条记录的修改
                            //        commentMap.put(key, value);
                            //        //将该行或该列的所有数据复制到内存中备份
                            //        //if (Pattern.matches("([A-Z]+)", rowOrColumn)){
                            //        //    //将 Excel 坐标转成数字索引号，且判定为列复制
                            //        //}else if (Pattern.matches("([0-9]+)", rowOrColumn)){
                            //        //    // 纯数字表示行移动
                            //        //}
                            //    }
                            //}
                            //第三种情况的配置参数合法性判断
                            String rowNumStr = value.trim();
                            if (!rowNumStr.matches("\\d+")) {
                                throw new Exception("\"专用配置批注\"中的\"区域配置\": " + value + "不正确");
                            }
                            //String[] regionPoints = new String[2];
                            //int ixxx = 0;
                            //if (Pattern.matches(regex, value)) {
                            //    Matcher matcher = compile3.matcher(value == null ? "" : value.trim());
                            //    int matcher_start = 0;
                            //    int total = 0;
                            //    while (matcher.find(matcher_start)) {
                            //        regionPoints[ixxx++] = matcher.group(1);
                            //        //System.out.println(matcher.group(1));
                            //        matcher_start = matcher.end();
                            //        total++;
                            //    }
                            //    //通过抛异常的方式向前端报出错误，如果配置参数有误的话
                            //    if (total != 2) {
                            //        //将该条记录标记为：失效
                            //        commentMap.put(key, "error");
                            //        throw new Exception("\"专用配置批注\"中的\"区域配置\": " + value + "不正确");
                            //    } else {
                            //        //将该条记录标记为：失效
                            //        commentMap.put(key, value);
                            //    }
                            //}
                            //////参数合法性判断结束
                        }
                    }
                }
            }
        }
        backupNeedMoveRegion(workbook);
        /*
        根据 2021 年 11 月新需求，原参变量定义格式为： $S1=sql，因待填充位置的标的 $S1 会导致该单元格无法被设置为数字格式，
        这样当整列为数字格式且填充完成后该单元格还是字符格式，因此现调整设计为，在 $S1(A5)=sql   $S1 后加一个括号来表示该sql的标的位，
        但同时也支持原格式，两种形式都兼容，以保证设计的兼容性
         */
        // pattern1 用于抽取括号中的内容
        String pattern1 = "(?<=\\()[^\\)]+";
        // pattern2 用于表达式合法性判断
        String pattern2 = "\\w+\\d+";
        // pattern3 用于拆分 excel 列坐标中的字母和数字
        String pattern3 = "(\\D+)(\\d+)";

        Set<String> keySet = varsMap.keySet();
        // 修正用的 repairedMap
        Map<String,String> repairedMap = new HashMap();
        for (String key : keySet) {
            if (key.contains("(") && key.contains(")")) {
                Pattern p1 = Pattern.compile(pattern1);
                Pattern p2 = Pattern.compile(pattern2);
                Pattern p3 = Pattern.compile(pattern3);
                Matcher m1 = p1.matcher(key);
                while (m1.find()) {
                    //本处不能报错，有错直接忽略
                    String posStrs = m1.group(0);
                    //这时 posStrs 为 $S1 所在的单元格组，用半角逗号分割,比如:A5,B6,C7，目前暂不支持区间，比如 A5-C5
                    if (!"".equals(posStrs)) {
                        //拆分坐标定位，存入位置map,VarPosMap
                        String[] posStr = posStrs.split(",+");
                        for (String pos : posStr) {
                            //判断每个拆分后的 excel 列坐标是否合法，必须为 A5，一个或多个字母+一个或多个数字
                            if (Pattern.matches(String.valueOf(p2), pos)) {
                                //再将类 AB52 的列坐标转成纯数字坐标,拆分字符和数字
                                Matcher m3 = p3.matcher(pos);
                                String enColPos = "";
                                String numRowPos = "";
                                if (m3.find()) {
                                    enColPos = m3.group(1);
                                    numRowPos = m3.group(2);
                                }
                                //取删除了 () 的key
                                String newKey = key.replaceFirst("\\(.+\\)", "");
                                //取行号,行号已取为 numRowPos
                                //取列号，
                                int numColPos = CellReference.convertColStringToIndex(enColPos);
                                //存放于 List<String> varsDefPos
                                String add2ListStr = newKey + "=" + (Integer.parseInt(numRowPos) - 1) + "," + numColPos;
                                //如果已经存在，则不再次添加
                                if (!varsDefPos.contains(add2ListStr)) {
                                    varsDefPos.add(add2ListStr);
                                    //同时 key 存于修复 map 中，用于将 $S1(B6)=sql，这种形式还原为 S$1=sql 这种形式的 varsMap
                                    repairedMap.put(key, newKey);
                                }
                            }
                        }
                    }
                }
            }
        }
        //删除 varsDefMap 中现有的定义，改为标准定义格式，
        repairedMap.forEach((key, newKey) -> {
            String oldValue = varsMap.get(key);
            if (oldValue != null && !"".equals(oldValue)) {
                varsMap.remove(key);
                varsMap.put(newKey,oldValue);
            }
        });
        return varsMap;
    }

    /*
      将需要移动的区域先备份到 needMove2VarsFilledEnd Map 中
     */
    public void backupNeedMoveRegion(Workbook workbook) {
        Set<String> commentMapKeySet = this.commentMap.keySet();
        for (String key : commentMapKeySet) {
            String[] split = key.split("@");
            int sheetIndex = Integer.parseInt(split[0]);
            String var$S = split[1];
            String region = commentMap.get(key);

            /////第一大步：拆解区域(例如:CD123XE123这个区域)到行列,拆解结果，得到 point1 和 point2 两个坐标点
            int point1x = 0;
            int point1y = 0;
            int point2x = 0;
            int point2y = 0;

            //一整行
            if (Pattern.matches("([0-9]+)", region)) {
                //暂不处理一整行
            }
            //接前一个if，下行表示一整列
            else if (Pattern.matches("([A-Z]+)", region)) {
                //暂不处理一整列
            }
            //接前一个 else if ,下行表示，指定区域，即 E3--F3(行区域) 或 E3--E20(列区域)
            else if (Pattern.matches("([A-Z]+\\d+){2}", region)) {
                //需要复制的坐标区域
                int[] copyArea = new int[4];
                //copyArea 需要使用的 索引号
                int ixx = 0;

                Matcher matcher = Pattern.compile("([A-Z]+\\d+)").matcher(region);
                int matcher_start = 0;
                int total = 0;
                while (matcher.find(matcher_start)) {
                    String _A1 = matcher.group(1);
                    //再次抽取 列 坐标
                    int matcherColumn_start = 0;
                    Matcher matcherColumnStr = Pattern.compile("([A-Z]+)").matcher(_A1);
                    Matcher matcherColumnNum = Pattern.compile("([0-9]+)").matcher(_A1);
                    while (matcherColumnStr.find(matcherColumn_start) && matcherColumnNum.find(matcherColumn_start)) {
                        //抽取  Excel 坐标中的行部分，例如：AB134 抽取其中的 134，并减 1(excel的行从1开始计数，程序中从0开始计数) ，得到程序中的真实可用的索引号
                        String cellCoordinateStrRowNum = matcherColumnNum.group(1);
                        int cellRowNum = Integer.parseInt(cellCoordinateStrRowNum) - 1;
                        copyArea[ixx++] = cellRowNum;

                        //将字母坐标转成数字索引号
                        String cellCoordinateStr = matcherColumnStr.group(1);
                        int cellColumnNum = CellReference.convertColStringToIndex(cellCoordinateStr);
                        copyArea[ixx++] = cellColumnNum;
                        matcherColumn_start = matcherColumnStr.end();
                    }
                    matcher_start = matcher.end();
                    total++;
                }


                /////第三大步， Excel 坐标交换
                /*
                  两个 Excel 坐标的起始点交换
                  配置的 Excel 坐标，有两种情况，一，是起点在左，终点在右。二是起点在右，终点在左，需要统一调整为起点在左，终点在右这种情况。
                  即在上一步的处理中，copyArea[0] 和 copyArea[1] 为坐标点 1 的 (y,x)，copyArea[2] 和 copyArea[3]，为坐标点 2 的(y,x)
                  如果，坐标点1 在坐标点2 的右边，则需要统一调整到，坐标点1
                 */
                //如果 (Point == p), p1.x > p2.x && p1.y > p2.y
                int tmp = 0;
                if (copyArea[0] > copyArea[2] && copyArea[1] > copyArea[3]) {
                    tmp = copyArea[2];
                    copyArea[2] = copyArea[0];
                    copyArea[0] = tmp;
                    tmp = copyArea[1];
                    copyArea[1] = copyArea[3];
                    copyArea[3] = tmp;
                } else if (copyArea[0] > copyArea[2] && copyArea[1] < copyArea[3]) {
                    tmp = copyArea[2];
                    copyArea[2] = copyArea[0];
                    copyArea[0] = tmp;
                } else if (copyArea[0] < copyArea[2] && copyArea[1] < copyArea[3]) {
                    tmp = copyArea[1];
                    copyArea[1] = copyArea[3];
                    copyArea[3] = tmp;
                }

                point1x = copyArea[0];
                point1y = copyArea[1];
                point2x = copyArea[2];
                point2y = copyArea[3];
            }

            //////第三大步，真实处理复制
            // 用于保存区域 cell 对象的 Map
            Map<Integer, List<BackupCell>> copyedValueMap = new LinkedHashMap();

            Sheet sheetN = workbook.getSheetAt(sheetIndex);
            for (int j = point1x; j <= point2x; j++) {
                Row row = sheetN.getRow(j);
                if (row == null) {
                    copyedValueMap.put(j, null);
                    continue;
                } else {
                    ArrayList<BackupCell> cellsList = new ArrayList<>();
                    for (int k = point1y; k <= point2y; k++) {
                        Cell cell = row.getCell(k);

                        //备份，然后再将该单元格设置为 null，表示一个移动操作
                        BackupCell backupCell = new BackupCell();
                        if (cell != null) {
                            backupCell.setColumnIndex(cell.getColumnIndex());
                            backupCell.setCellStyle(cell.getCellStyle());
                            backupCell.setCellType(cell.getCellType());
                            switch (cell.getCellType()) {
                                case Cell.CELL_TYPE_NUMERIC:
                                    backupCell.setValueDouble(cell.getNumericCellValue());
                                    break;
                                case Cell.CELL_TYPE_STRING:
                                    backupCell.setValueString(cell.getStringCellValue());
                                    break;
                                case Cell.CELL_TYPE_FORMULA:
                                    backupCell.setFormula(cell.getCellFormula());
                                    break;
                                case Cell.CELL_TYPE_BLANK:
                                    break;
                                case Cell.CELL_TYPE_BOOLEAN:
                                    backupCell.setBooleanValue(cell.getBooleanCellValue());
                                    break;
                                case Cell.CELL_TYPE_ERROR:
                                    backupCell.setErrorValue(cell.getErrorCellValue());
                                    break;
                            }
                            //backupCell.setComment(cell.getCellComment());
                            try {
                                backupCell.setDate(cell.getDateCellValue());
                            } catch (Exception e) {
                                //e.printStackTrace();
                            }
                            try {
                                backupCell.setHyperlink(cell.getHyperlink());
                            } catch (Exception e) {
                                //e.printStackTrace();
                            }
                            try {
                                backupCell.setRichTextString(cell.getRichStringCellValue());
                            } catch (Exception e) {
                                // e.printStackTrace();
                            }
                            //设置宽高
                            backupCell.setColumnWidth((short) cell.getSheet().getColumnWidth(cell.getColumnIndex()));
                            backupCell.setRowHeight(cell.getRow().getHeight());
                        }
                        cellsList.add(backupCell);
                        ///if(cell !=null){
                        ///    cell.getRow().removeCell(cell);
                        ///}
                    }
                    copyedValueMap.put(j, cellsList);
                }
            }
            needMove2VarsFilledEnd.put(key, copyedValueMap);
        }
    }


    /**
     * 根据调用方提供的参变量解析结果，将所有参变量填充到由 excel 模板生成的实例中，
     * 实例的文件名只能为,xls,xlsx
     *
     * @param workbook
     * @param excelOutputFilePath excel实例文件输出路径
     * @param vars                参变量集合
     * @param addinMap            附加参数列表,增加填充完毕后的可能填充标记，用于后续操作
     * @return 是否填充成功
     */
    public void fillVars2ExcelInstance(Workbook workbook, String
            excelOutputFilePath, Map<String, Object> vars, Map addinMap) throws Exception {

        // 获取Excel后缀名
        //String fileType = excelOutputFilePath.substring(excelOutputFilePath.lastIndexOf(".") + 1, excelOutputFilePath.length());
        // 获取Excel文件
        File excelFile = new File(excelOutputFilePath);
        if (!excelFile.exists()) {
            try {
                excelFile.createNewFile();
            } catch (IOException e) {
                LOG.error("创建 excel 报表文件时发生错误");
                e.printStackTrace();
            }
        }
        //根据后缀名转转文件类型，暂时未作处理，HSSFWorkbook对应 xls 后缀，XSSFWorkbook对应 xlsx 后缀，判断，如果 workbook 为 HSSWorkbook 类型，输出
        //excel 后缀如果为 xlsx，或如果 workbook 为 XHSSWorkbook，则理论上需要进行转换，但实际上新的 excel 能够打开旧的 xls 文件，但旧的 excel 不能打开新的 xlsx 文件，为减少工作量计，
        //如果最后生成的 excel 文件不能在线下使用 excel.exe 打开，则由业务人员自行处理。

        //复制一个 workbook 对象，此处应当使用对象克隆，但 Workbook 未实现 serializable 接口，无法使用序列化反序列化的接口
        //这里拟使用 BeanUtil.copyProperties()方法 //TODO

        //Workbook clonedWorkbook = ExcelUtil.clone(workbook);


        //参变量合法性判断
        if (vars == null || 0 == vars.size() || varsDefPos == null || 0 == varsDefPos.size()) {
            //如果未定义任何参变量,则输出一个空的 excel 文件
            try {
                FileOutputStream fileOutputStream = new FileOutputStream(excelFile);
                workbook.write(fileOutputStream);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
                return;
            } catch (IOException e) {
                e.printStackTrace();
                return;
            } finally {
                //以下保留，新版本的 poi jar 包中需要使用 workbook.close()对流文档进行关闭
                //默认直接关闭掉 workbook 对象,因为 workbook 对象在填充之后，其内容已经被污染，不建议再次使用
                // try {
                //     if (null != clonedWorkbook) {
                //         workbook.close();
                //     }
                // } catch (Exception e) {
                //     LOG.warn("关闭数据流出错！错误信息：" + e.getMessage());
                //     return false;
                // }
            }
            return;
        }

        /*
          处理矩阵，行式矩阵--表示数组型参变量从左到右逐次填充，列式矩阵--表示数组型参变量从上到下逐次填充
         */
        //获取list中的待填充参变量位置map
        //List<String> varPosList = (List<String>) varsDefPos.get(0);
        //定义所有矩阵填充的类型map
        HashMap<String, String> typeMap = new HashMap<>();
        //遍历分析所有配对的矩阵参变量
        for (String posStr : varsDefPos) {
            String[] split = posStr.split("=");
            String key = split[0];
            String $Sxx = key;
            String value = split[1];
            if ($Sxx.contains("$S")) {

                boolean be$S$T = false;
                //单独处理同一单元格同时带有 $S 和 $T 这种情况
                if ($Sxx.contains("$S") && $Sxx.contains("$T")) {
                    be$S$T = true;
                    //先将 $S$T替换为仅 $S ,方便后续统一处理
                    $Sxx = $Sxx.replace("$T", "");
                }

                String $Txx = $Sxx.replace("$S", "$T");
                String $Exx = $Sxx.replace("$S", "$E");
                //分别获取 $Sxx 和 $Txx 的坐标
                String $SxxPos = "";
                String $TxxPos = "";
                String $ExxPos = "";
                //从 posList 获取 $SxxPos
                $SxxPos = value;

                if (be$S$T) {
                    $TxxPos = value;
                } else {
                    //从 posList 获取 $TxxPos
                    for (String s : varsDefPos) {
                        if (s.contains($Txx)) {
                            String[] split1 = s.split("=");
                            if (split1.length > 1) {
                                $TxxPos = split1[1];
                            }
                        }
                    }
                }

                //从 posList 获取 $ExxPos
                for (String s : varsDefPos) {
                    if (s.contains($Exx)) {
                        String[] split1 = s.split("=");
                        if (split1.length > 1) {
                            $ExxPos = split1[1];
                        }
                    }
                }

                //拆分各变量坐标为 x 和 y 变量
                String[] $SxxPosSplit = $SxxPos.split(",");
                //定义用于计算最大允许填充区域的数值
                int tmp$Sx = Integer.parseInt($SxxPosSplit[0]);
                int tmp$Sy = Integer.parseInt($SxxPosSplit[1]);
                Integer tmp$Tx = null;
                Integer tmp$Ty = null;
                Integer tmp$Ex = null;
                Integer tmp$Ey = null;

                String[] $TxxPosSplit = $TxxPos.split(",");
                if (!"".equals($TxxPos)) {
                    tmp$Tx = Integer.parseInt($TxxPosSplit[0]);
                    tmp$Ty = Integer.parseInt($TxxPosSplit[1]);
                }
                String[] $ExxPosSplit = $ExxPos.split(",");
                if (!"".equals($ExxPos)) {
                    tmp$Ex = Integer.parseInt($ExxPosSplit[0]);
                    tmp$Ey = Integer.parseInt($ExxPosSplit[1]);
                }

                //判断 $Sxx 是否与 $Txx 在同一单元格中
                //varsDefMap: @$Sxxx -> select loan_cur_blc , loan_lt_blc from rep_loan_nei where data_date = "2020/08/" order by order_no;

                /* 情况1. $S 存在，$E 不存在，$T 不存在,则填充以 $S 位置为起点，
                 按 sql 语句的结果集列，从左向右，列数不限长分布，且从上到下逐次填充
                 不限制记录条数
                 */
                if (!"".equals($SxxPos) && ("".equals($TxxPos) & "".equals($ExxPos))) {
                    typeMap.put($Sxx, "column@" + $SxxPos + "_" + 0 + "," + 0);
                    continue;
                }
                /* 情况2. $S 存在，$E 存在，$T 存在,则根据 $S -> $T 判断是行式填充还是列是填充，
                 按 sql 语句的结果集列，从左向右，sql列数填充仅填充到 $T 位置，如果 sql 列与 $S->$T
                 之间的列数不一致，则报错，且从上到下逐次填充，但不限制记录条数
                 */
                else if (!"".equals($SxxPos) && !"".equals($TxxPos)) {
                    //坐标位置合法性判断
                    if (tmp$Tx != null && tmp$Ty != null && (tmp$Tx - tmp$Sx == 0 && tmp$Ty - tmp$Sy < 0) || (tmp$Ty - tmp$Sy == 0 && tmp$Tx - tmp$Sx < 0)) {
                        throw new Exception("参变量 $S $T 定义的位置有误,$T 不允许定义在 $S 的左边或者上边");
                    }

                    //判断是行式矩阵还是列式矩阵
                    if ($SxxPosSplit[0].equals($TxxPosSplit[0])) {
                        //计算最大允许被填充的 sql 列计数，即只允许sql结果集中的 n 列用于填充到 excel
                        int maxSqlCols = tmp$Ty - tmp$Sy;
                        //判断 $S $E 是否在同一列，如果不在同一列，视为未定义
                        int maxSqlRows = 0;
                        if (!"".equals($ExxPos)) {
                            //坐标位置合法性判断
                            if (tmp$Ex != null && tmp$Ey != null && (tmp$Ey - tmp$Sy == 0 && tmp$Ex - tmp$Sx < 0)) {
                                throw new Exception("参变量 $S $E 定义的位置有误,$E 不允许定义在 $S 的左边或者上边");
                            }
                            maxSqlRows = (tmp$Ex - tmp$Sx) + 1;
                        }
                        typeMap.put($Sxx, "column@" + $SxxPos + "_" + maxSqlRows + "," + (maxSqlCols + 1));
                    } else if ($SxxPosSplit[1].equals($TxxPosSplit[1])) {
                        //计算最大允许被填充的 sql 列计数，即只允许sql结果集中的 n 列用于填充到 excel
                        int maxSqlRows = tmp$Tx - tmp$Sx;
                        //计算最大允许填充的行计数
                        int maxSqlCols = 0;
                        if (!"".equals($ExxPos)) {
                            //坐标位置合法性判断
                            if (tmp$Ex != null && tmp$Ey != null && (tmp$Ex - tmp$Sx == 0 && tmp$Ey - tmp$Sy < 0)) {
                                throw new Exception("参变量 $S $E 定义的位置有误,$E 不允许定义在 $S 的左边或者上边");
                            }
                            maxSqlCols = (tmp$Ey - tmp$Sy) + 1;
                        }
                        typeMap.put($Sxx, "row@" + $SxxPos + "_" + (maxSqlRows + 1) + "," + maxSqlCols);
                    }
                    continue;
                }

                /* 情况3. $S 存在，$E 不存在，$T 不存在,则根据 $S -> $E 判断是行式填充还是列是填充，
                 按 sql 语句的结果集列，从左向右，sql列数填充不限制，如果 sql 列与 $S->$E
                 之间的行数不一致，则报错，且从左到右逐次填充
                 */
                else if (!"".equals($SxxPos) && (!"".equals($ExxPos) & "".equals($TxxPos))) {
                    //坐标位置合法性判断
                    if (tmp$Ex != null && tmp$Ey != null && (tmp$Ex - tmp$Sx == 0 && tmp$Ey - tmp$Sy < 0) || (tmp$Ey - tmp$Sy == 0 && tmp$Ex - tmp$Sx < 0)) {
                        throw new Exception("参变量 $S $E 定义的位置有误,$E 不允许定义在 $S 的左边或者上边");
                    }
                    // 判断是行式矩阵还是列式矩阵
                    // 判断 $S $E 是否在同一列，如果不在同一列，视为未定义
                    if ($SxxPosSplit[0].equals($ExxPosSplit[0])) {
                        //计算最大允许被填充的 sql 列计数，即只允许sql结果集中的 n 列用于填充到 excel
                        int maxSqlCols = 0;
                        int maxSqlRows = tmp$Ey - tmp$Sy;
                        typeMap.put($Sxx, "row@" + $SxxPos + "_" + maxSqlCols + "," + (maxSqlRows + 1));
                    } else if ($SxxPosSplit[1].equals($ExxPosSplit[1])) {
                        //计算最大允许被填充的 sql 列计数，即只允许sql结果集中的 n 列用于填充到 excel
                        int maxSqlRows = 0;
                        //计算最大允许填充的行计数
                        int maxSqlCols = tmp$Ex - tmp$Sx;
                        typeMap.put($Sxx, "column@" + $SxxPos + "_" + (maxSqlCols + 1) + "," + maxSqlRows);
                    }
                    continue;
                }
            }
        }

        //填充参变量
        for (Map.Entry<String, Object> entry : vars.entrySet()) {
            //抽取要用的变量
            String key = entry.getKey();
            String[] varsStr = key.split("@");
            String realVarName = varsStr[1];
            int filledTotal = 0;

            if (key.contains("$S")) {//表示矩阵填充
                if (!(vars.get(key) instanceof Map)) {
                    //填充 sql error 到错误表达式处
                    continue;
                }
                Map<String, List<Object>> value = (Map<String, List<Object>>) entry.getValue();
                Set<String> keySet = typeMap.keySet();
                for (String var : keySet) {
                    String typePos = typeMap.get(var);
                    int sheetIndex = 0;
                    int rowIndex = 0;
                    int columnIndex = 0;
                    int maxA = 0;
                    int maxB = 0;
                    String varName = "";

                    //如果待填充的参变量和要填充的参变量一致，则开始填充
                    //var=0@$Sxxx;
                    if (var.equals(key)) {
                        String[] varSplit = var.split("@");
                        sheetIndex = Integer.parseInt(varSplit[0]);
                        varName = varSplit[1];
                        //判断该填充是从左到右的行填充，还是从上到下的列填充
                        String[] split = typePos.split("@");
                        String type = split[0];
                        String posStr = split[1];
                        String[] pos2Str = posStr.split("_");
                        String[] posXy = pos2Str[0].split(",");
                        String[] maxRowCol = pos2Str[1].split(",");
                        rowIndex = Integer.parseInt(posXy[0]);
                        columnIndex = Integer.parseInt(posXy[1]);
                        maxA = Integer.parseInt(maxRowCol[0]);
                        maxB = Integer.parseInt(maxRowCol[1]);
                        int tmpMax = 0;

                        //////////////    拆解参变量到 Excel 的 sheet row column   ///////////////
                        //判断该参变量名是否为 $S 打头，如果是，则表示为矩阵参变量
                        Sheet sheet = workbook.getSheetAt(sheetIndex);
                        if ("row".equals(type)) {
                            Set<String> sqlCloumnsSet = value.keySet();
                            //最大允许列判断
                            if (maxA != 0 && sqlCloumnsSet != null && sqlCloumnsSet.size() != maxA) {
                                throw new Exception(varName + "的SQL查询的列数与报表待填充的单元格总高度不相等");
                            }
                            for (String sqlkey : sqlCloumnsSet) {
                                List<Object> sqlColResultList = value.get(sqlkey);
                                //行号定，列号增
                                Row row = sheet.getRow(rowIndex);
                                if (row == null) {
                                    row = sheet.createRow(rowIndex);
                                }
                                int maxCount = row.getLastCellNum() + 1 < sqlColResultList.size() ? sqlColResultList.size() : row.getLastCellNum() + 1;
                                if (maxB != 0) {
                                    //不报错，直接截掉
                                    //throw new Exception(varName + "的SQL查询的结果数与报表待填充的单元格总宽度不相等");
                                    maxCount = maxB > maxCount ? maxCount : maxB;
                                } else {
                                    maxCount = sqlColResultList.size();
                                }
                                for (int i = columnIndex, t = 0; i < maxCount + columnIndex; i++, t++) {
                                    Cell cell = row.getCell(i);
                                    if (cell == null) {
                                        cell = row.createCell(i);
                                        //FixME 当获取到列最大值以外的边界时，创建可能会不成功，这需要测试
                                        Object fieldee = sqlColResultList.get(t);
                                        if (fieldee instanceof String) {
                                            cell.setCellType(Cell.CELL_TYPE_STRING);
                                            if (fieldee == null || "".equals(fieldee)) {
                                                cell.setCellValue("");
                                            } else {
                                                cell.setCellValue((String) fieldee);
                                            }
                                        } else if (fieldee instanceof BigDecimal) {
                                            cell.setCellType(Cell.CELL_TYPE_NUMERIC);
                                            cell.setCellValue(((BigDecimal) fieldee).doubleValue());
                                        }
                                    } else {
                                        //处理 $/ 跳过不填充
                                        if ("$/".equals(getCellStringValue(cell))) {
                                            cell.setCellType(Cell.CELL_TYPE_STRING);
                                            cell.setCellValue("");
                                        } else {
                                            /*根据2021-11-02 新需求，即当公式存在于数据填充区时，不更改公式，即不允许数据覆盖公式*/
                                            int cellType = cell.getCellType();
                                            if (cellType != Cell.CELL_TYPE_FORMULA) {
                                                Object fieldee = sqlColResultList.get(t);
                                                if (fieldee instanceof String) {
                                                    cell.setCellType(Cell.CELL_TYPE_STRING);
                                                    if (fieldee == null || "".equals(fieldee)) {
                                                        cell.setCellValue("");
                                                    } else {
                                                        cell.setCellValue((String) fieldee);
                                                    }
                                                } else if (fieldee instanceof BigDecimal) {
                                                    cell.setCellType(Cell.CELL_TYPE_NUMERIC);
                                                    cell.setCellValue(((BigDecimal) fieldee).doubleValue());
                                                }
                                            }
                                        }
                                    }
                                    filledTotal = i;
                                }
                                rowIndex++;
                            }
                        } else if ("column".equals(type)) {
                            //列号定，行号增
                            Set<String> sqlCloumnsSet = value.keySet();
                            //最大允许列判断，需要减去 $S1 所在的位置
                            if (maxB != 0 && sqlCloumnsSet != null && sqlCloumnsSet.size() != maxB) {
                                throw new Exception(varName + "的SQL查询的列总数与报表待填充单元格总宽度不相等");
                            }
                            //填充前，先将需要移动的 Excel 行或列进行移动
                            //先求最大行计数
                            int maxTotalCount = 0;
                            for (String sqlkey : sqlCloumnsSet) {
                                int size = value.get(sqlkey).size();
                                maxTotalCount = size > maxTotalCount ? size : maxTotalCount;
                            }

                            final int filledTotalFinal = maxTotalCount;
                            if (maxTotalCount > 0 && this.commentMap.size() > 0) {
                                //needMove2VarsFilledEnd.forEach((key2, value2) -> {
                                //    if (key2.equals(key)) {
                                //        //key3=需要复制的第几行
                                //        ((LinkedHashMap) value2).forEach((key3, value3) -> {
                                //            //Row row_ = sheet.getRow(filledTotalFinal + 1);
                                //        });
                                //    }
                                //});
                                //sheet.shiftRows(5, 5, maxTotalCount-2, true, true);
                                int startIndex = 0;
                                for (String varsDefPo : varsDefPos) {
                                    String[] splitX = varsDefPo.split("=");
                                    String varName2 = splitX[0];
                                    if (varName2.equals(key)) {
                                        startIndex = Integer.parseInt(varsDefPo.split("=")[1].split(",")[0]);
                                    }
                                }
                                final int startIndexFinal = startIndex;
                                int needCopyRowNumStr = 0;
                                for (String keyx : commentMap.keySet()) {
                                    if (keyx.equals(key)) {
                                        needCopyRowNumStr = Integer.parseInt(commentMap.get(keyx));
                                    }
                                }
                                needCopyRowNumStr--;
                                int movedTargetRowOffset = filledTotalFinal + startIndexFinal - needCopyRowNumStr;
                                sheet.shiftRows(needCopyRowNumStr, needCopyRowNumStr, movedTargetRowOffset, true, true);
                                int movedTargetRowNum = movedTargetRowOffset + needCopyRowNumStr;
                                //循环重新计算并设置公式起始位置，保证公式正确
                                Row row = sheet.getRow(movedTargetRowNum);
                                //获取开始公式开始行和公式结束行
                                int formulaStarRow = rowIndex;
                                int formulaEndRow = filledTotalFinal + rowIndex;
                                if (row != null) {
                                    short firstCellNum = row.getFirstCellNum();
                                    short lastCellNum = row.getLastCellNum();
                                    //使用正则替换字符内容
                                    for (int i = firstCellNum; i < lastCellNum; i++) {
                                        Cell cell = row.getCell(i);
                                        if (cell == null) {
                                            continue;
                                        } else {
                                            String cellFormula = null;
                                            try {
                                                cellFormula = cell.getCellFormula();
                                                if (!"".equals(cellFormula) && cellFormula.contains("SUM")) {
                                                    //样例:cellFormula = "SUM(C5:C1000)";
                                                    String coorditionStr = cellFormula.replace("SUM(", "").replace(")", "");
                                                    String[] split1 = coorditionStr.split(":");
                                                    String StrRegex = "([A-Z]+)";
                                                    String NumRegex = "([0-9]+)";
                                                    //抽取 字符
                                                    String finalColumnStr1 = "";
                                                    String finalRowStr1 = "";
                                                    String finalColumnStr2 = "";
                                                    String finalRowStr2 = "";
                                                    Matcher matcher = Pattern.compile(StrRegex).matcher(split1[0]);
                                                    int matcher_start = 0;
                                                    while (matcher.find(matcher_start)) {
                                                        finalColumnStr1 = matcher.group(1);
                                                        matcher_start = matcher.end();
                                                    }

                                                    //Matcher matcher2 = Pattern.compile(NumRegex).matcher(split1[0]);
                                                    //int matcher_start2 = 0;
                                                    //while (matcher2.find(matcher_start2)) {
                                                    //    finalRowStr1 = matcher2.group(1);
                                                    //    matcher_start2 = matcher2.end();
                                                    //}
                                                    Matcher matcher3 = Pattern.compile(StrRegex).matcher(split1[1]);
                                                    int matcher_start3 = 0;
                                                    while (matcher3.find(matcher_start3)) {
                                                        finalColumnStr2 = matcher3.group(1);
                                                        matcher_start3 = matcher3.end();
                                                    }

                                                    //Matcher matcher4 = Pattern.compile(NumRegex).matcher(split1[1]);
                                                    //int matcher_start4 = 0;
                                                    //while (matcher4.find(matcher_start4)) {
                                                    //    finalRowStr2 = matcher4.group(1);
                                                    //    matcher_start4 = matcher4.end();
                                                    //}
                                                    //重组公式
                                                    String formulaRewrite = "SUM(" + finalColumnStr1 + formulaStarRow + ":" + finalColumnStr2 + formulaEndRow + ")";
                                                    cell.setCellFormula(formulaRewrite);
                                                }
                                            } catch (Exception e) {
                                                //e.printStackTrace();
                                            }

                                        }
                                    }
                                }
                            }

                            for (String sqlkey : sqlCloumnsSet) {
                                List<Object> sqlColResultList = value.get(sqlkey);
                                int maxCount = 0;//sheet.getLastRowNum() + 1 > sqlCloumns.size() ? sqlCloumns.size() : sheet.getLastRowNum() + 1;
                                //maxA=0 表示未使用 $Exxx 作为结束标签，即可以任意长度填充
                                if (maxA != 0) {
                                    maxCount = maxA < sqlColResultList.size() ? maxA : sqlColResultList.size();
                                } else {
                                    maxCount = sqlColResultList.size();
                                }

                                for (int i = rowIndex, t = 0; t < 1048576 && t < maxCount; i++, t++) {
                                    Row row = sheet.getRow(i);
                                    if (row == null) {
                                        row = sheet.createRow(i);
                                    }
                                    Cell cell = row.getCell(columnIndex);
                                    if (cell == null) {
                                        cell = row.createCell(columnIndex);
                                        //FixME 当获取到列最大值以外的边界时，创建可能会不成功，这需要测试
                                        Object fieldee = sqlColResultList.get(t);
                                        if (fieldee instanceof String) {
                                            cell.setCellType(Cell.CELL_TYPE_STRING);
                                            if (fieldee == null || "".equals(fieldee)) {
                                                cell.setCellValue("");
                                            } else {
                                                cell.setCellValue((String) fieldee);
                                            }
                                        } else if (fieldee instanceof BigDecimal) {
                                            cell.setCellType(Cell.CELL_TYPE_NUMERIC);
                                            cell.setCellValue(((BigDecimal) fieldee).doubleValue());
                                        }
                                    } else {
                                        //处理 $/ 跳过不填充
                                        if ("$/".equals(getCellStringValue(cell))) {
                                            cell.setCellType(Cell.CELL_TYPE_STRING);
                                            cell.setCellValue("");
                                        } else {
                                            /*根据2021-11-02 新需求，即当公式存在于数据填充区时，不更改公式，即不允许数据覆盖公式*/
                                            int cellType = cell.getCellType();
                                            if (cellType != Cell.CELL_TYPE_FORMULA) {
                                                Object fieldee = sqlColResultList.get(t);
                                                if (fieldee instanceof String) {
                                                    cell.setCellType(Cell.CELL_TYPE_STRING);
                                                    if (fieldee == null || "".equals(fieldee)) {
                                                        cell.setCellValue("");
                                                    } else {
                                                        cell.setCellValue((String) fieldee);
                                                    }
                                                } else if (fieldee instanceof BigDecimal) {
                                                    cell.setCellType(Cell.CELL_TYPE_NUMERIC);
                                                    cell.setCellValue(((BigDecimal) fieldee).doubleValue());
                                                }
                                            }
                                        }
                                    }
                                    filledTotal = i;
                                }
                                columnIndex++;
                            }
                            //Row row1 = sheet.getRow(841);
                            //if (row1 != null) {
                            //    sheet.removeRow(row1);
                            //}
                        /*    //FixMe 查询 needMove2VarsFilledEnd
                            final int filledTotalFinal = filledTotal;
                            //key= 0@$S1, value=copyedValueMap(key=需要复制的第几行，value=list<BackupCell>
                            if (filledTotalFinal != 0) {
                                needMove2VarsFilledEnd.forEach((key2, value2) -> {
                                    if (key2.equals(key)) {
                                        //key3=需要复制的第几行
                                        ((LinkedHashMap) value2).forEach((key3, value3) -> {
                                            Row row_ = sheet.getRow(filledTotalFinal + 1);
                                            if (row_ == null) {
                                                row_ = sheet.createRow(filledTotalFinal + 1);
                                            }
                                            final Row row = row_;
                                            ((ArrayList) value3).forEach(cell -> {
                                                BackupCell backupCell = (BackupCell) cell;
                                                Cell copyCell = row.getCell(backupCell.getColumnIndex());
                                                if (copyCell == null) {
                                                    copyCell = row.createCell(backupCell.getColumnIndex());
                                                }
                                                copyCell.setCellType(backupCell.getCellType());
                                                switch (copyCell.getCellType()) {
                                                    case Cell.CELL_TYPE_NUMERIC:
                                                        copyCell.setCellValue(backupCell.getValueDouble());
                                                        break;
                                                    case Cell.CELL_TYPE_STRING:
                                                        copyCell.setCellValue(backupCell.getValueString());
                                                        break;
                                                    case Cell.CELL_TYPE_BLANK:
                                                        break;
                                                    case Cell.CELL_TYPE_FORMULA:
                                                        copyCell.setCellFormula(backupCell.getFormula());
                                                        break;
                                                    case Cell.CELL_TYPE_BOOLEAN:
                                                        copyCell.setCellValue(backupCell.isBooleanValue());
                                                        break;
                                                    case Cell.CELL_TYPE_ERROR:
                                                        copyCell.setCellErrorValue(backupCell.getErrorValue());
                                                        break;
                                                }
                                                //TODO 检查修复
                                                if (backupCell.getHyperlink() != null) {
                                                    copyCell.setHyperlink(backupCell.getHyperlink());
                                                }
                                                //暂不处理批注，因批注处理总是报异常
                                                //if (backupCell.getComment() != null) {
                                                //    copyCell.setCellComment(backupCell.getComment());
                                                //}

                                                if (backupCell.getDate() != null) {
                                                    copyCell.setCellValue(backupCell.getDate());
                                                }
                                                //样式拷贝
                                                copyCell.setCellStyle(backupCell.getCellStyle());
                                                //设置宽高,此处为从上到下填充数据,仅可修改行的高度,不能修改列的宽度,故,此处暂不设置列宽
                                                copyCell.getRow().setHeight(backupCell.getRowHeight());

                                            });
                                        });
                                    }
                                });
                            }*/
                        }
                    }
                }
            } else {
                //遍历填充所有非矩阵参量
                for (String posStr : varsDefPos) {
                    String value2 = vars.get(key).toString();
                    String varName = "";
                    int sheetIndex = 0;
                    int rowIndex = 0;
                    int columnIndex = 0;

                    //全局变量，对所有 sheet 适用，在此处将全局变量前的 _@ 符更换为当前 sheet 序号@，即：由 _@#xxx 更换为当前的 sheet 序号如 0@#xxx
                    String replacedKey = key;
                    if (replacedKey.contains("_@")) {
                        replacedKey = replacedKey.replace("_@", "" + sheetIndex + "@");
                    }

                    if (posStr.contains(replacedKey)) {
                        String[] varPosStr = posStr.split("=");
                        if (varPosStr.length == 2) {
                            String[] sheetVars = varPosStr[0].split("@");
                            sheetIndex = Integer.parseInt(sheetVars[0]);
                            varName = sheetVars[1];
                            String[] posXY = varPosStr[1].split(",");
                            rowIndex = Integer.parseInt(posXY[0]);
                            columnIndex = Integer.parseInt(posXY[1]);
                        }
                        Sheet sheetNum = workbook.getSheetAt(sheetIndex);
                        Row row = sheetNum.getRow(rowIndex);
                        if (row == null) {
                            row = sheetNum.createRow(rowIndex);
                        }
                        Cell cell = row.getCell(columnIndex);
                        //FixME 当获取到列最大值以外的边界时，创建可能会不成功，这需要测试
                        if (cell == null) {
                            cell = row.createCell(columnIndex);
                        }
                        cell.setCellType(Cell.CELL_TYPE_STRING);
                        cell.setCellValue(value2);
                    } else if (posStr.contains("_multi")) {
                        String[] varPosStr = posStr.split("=");
                        if (varPosStr.length == 2) {
                            String[] sheetVars = varPosStr[0].split("@");
                            sheetIndex = Integer.parseInt(sheetVars[0]);
                            varName = sheetVars[1];
                            String[] posXY = varPosStr[1].split(",");
                            rowIndex = Integer.parseInt(posXY[0]);
                            columnIndex = Integer.parseInt(posXY[1]);
                        }
                        Sheet sheetNum = workbook.getSheetAt(sheetIndex);
                        Row row = sheetNum.getRow(rowIndex);
                        if (row == null) {
                            row = sheetNum.createRow(rowIndex);
                        }
                        Cell cell = row.getCell(columnIndex);
                        //FixME 当获取到列最大值以外的边界时，创建可能会不成功，这需要测试
                        if (cell == null) {
                            cell = row.createCell(columnIndex);
                        }
                        //循环替换字符串内容,每次最外层大 forEach 循环替换一个
                        String cellStringValue = getCellStringValue(cell);
                        String replaced = cellStringValue.replace(realVarName, value2);
                        cell.setCellType(Cell.CELL_TYPE_STRING);
                        cell.setCellValue(replaced);
                    }
                }
            }
            //单元处理同一单元格中包含多个参变量的情况
        }
        //后续处理
        // 如果为“预览”时，清除补录区的作色，强制转换为“白色”
        if ("review".equalsIgnoreCase((String) addinMap.get("flag"))) {
            highlightRewriteRegion(workbook, (short) 0);
        }
        // 补录模板编辑
        if ("rewriteDesign".equalsIgnoreCase((String) addinMap.get("flag"))) {
            //当为“补录”时，填充高亮区域
            highlightRewriteRegion(workbook, HSSFColor.PALE_BLUE.index);
            //将“补录模板”中的数据复制到实例 excel 文件中
            //rewriteAreaCopy2Temp((String) addinMap.get("rewriteTempPath"));
        }
        // 补录
        if ("rewrite".equalsIgnoreCase((String) addinMap.get("flag"))) {
            //当为“补录”时，填充高亮区域
            highlightRewriteRegion(workbook, HSSFColor.PALE_BLUE.index);
            //将“补录模板”中的数据复制到实例 excel 文件中
            //当前端选择“本次不使用补录数据”（或相同含义操作）时，不将补录数据复制过来
            if (addinMap != null && ("none".equalsIgnoreCase((String) addinMap.get("reset")) | !"reset".equalsIgnoreCase((String) addinMap.get("reset")))) {
                rewriteAreaCopy2Temp((String) addinMap.get("rewriteTempPath"));
            }
        }

        //清理 workbook 中所有未被填充的 $ 或 # 打头的参变量
        clearExcelAllVars(workbook);
        //输出文件
        try {
            FileOutputStream fileOutputStream = new FileOutputStream(excelFile);
            workbook.write(fileOutputStream);
        } catch (
                FileNotFoundException e) {
            e.printStackTrace();
            return;
        } catch (
                IOException e) {
            e.printStackTrace();
            return;
        } finally {
            //以下保留，新版本的 poi jar 包中需要使用 workbook.close()对流文档进行关闭
            //默认直接关闭掉 workbook 对象,因为 workbook 对象在填充之后，其内容已经被污染，不建议再次使用
            // try {
            //     if (null != clonedWorkbook) {
            //         workbook.close();
            //     }
            // } catch (Exception e) {
            //     LOG.warn("关闭数据流出错！错误信息：" + e.getMessage());
            //     return false;
            // }
        }
        return;
    }

    /**
     * 清除从 excel 模板的内存对象的复制对象 workbook 中的参变量定义内容
     * 并执行报表中所有存在的公式
     *
     * @param workBook
     * @return
     */
    public void clearExcelDefineVars(Workbook workBook) {
        //创建公式执行对象，用于执行报表中的公式
        FormulaEvaluator evaluator = workBook.getCreationHelper().createFormulaEvaluator();
        //开始遍历每个单元格
        for (int i = 0; i < workBook.getNumberOfSheets(); i++) {
            Sheet sheetN = workBook.getSheetAt(i);
            for (int j = 0; j < sheetN.getLastRowNum() + 1; j++) {
                Row row = sheetN.getRow(j);
                if (row == null) {
                    continue;
                }
                for (int k = 0; k < row.getLastCellNum() + 1; k++) {
                    Cell cell = row.getCell(k);
                    String varsName = null;
                    try {
                        varsName = getCellStringValue(cell);
                        //此处出现的正则表达式仅在一个=号时正确和连续多个=号是不正确，但未匹配不连续多个=号的情况
                        if (varsName != null && !"".equals(varsName.trim()) && (varsName.startsWith("$") && varsName.matches("^[^=]*=[^=]*$")) ||
                                varsName.startsWith("#")) {
                            cell.setCellType(Cell.CELL_TYPE_STRING);
                            cell.setCellValue("");
                        }
                    } catch (RuntimeException e) {
                    }
                    //计算报表中所有存在的公式
                    if (cell != null && cell.getCellType() == Cell.CELL_TYPE_FORMULA) {
                        try {
                            evaluator.evaluateFormulaCell(cell);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }

    /**
     * 清除从 excel 模板的内存对象的复制对象 workbook 中的参变量
     * 并执行报表中所有存在的公式
     *
     * @param workBook
     * @return
     */
    private void clearExcelAllVars(Workbook workBook) {
        //创建公式执行对象，用于执行报表中的公式
        FormulaEvaluator evaluator = workBook.getCreationHelper().createFormulaEvaluator();
        for (int i = 0; i < workBook.getNumberOfSheets(); i++) {
            Sheet sheetN = workBook.getSheetAt(i);
            for (int j = 0; j < sheetN.getLastRowNum() + 1; j++) {
                Row row = sheetN.getRow(j);
                if (row == null) {
                    continue;
                }
                for (int k = 0; k < row.getLastCellNum() + 1; k++) {
                    Cell cell = row.getCell(k);
                    String varsName = null;
                    try {
                        varsName = getCellStringValue(cell);
                        //此处出现的正则表达式仅在一个=号时正确和连续多个=号是不正确，但未匹配不连续多个=号的情况,
                        //FIXME 不严谨判断，会误删除形如 "$org有限公司" 这种情况，本意是要删除 $org，实际清除了整个单元格
                        //Fixme 但实际上这种情况极少会遇到，
                        if (varsName != null && !"".equals(varsName.trim()) && varsName.startsWith("$") || varsName.startsWith("#")) {
                            cell.setCellType(Cell.CELL_TYPE_STRING);
                            cell.setCellValue("");
                        } else if (varsName != null && !"".equals(varsName.trim()) && varsName.contains("$")) {
                            //处理某一单元格不是开头就是 $xxx 或 #xxx 的情况，而是参变量直接包含在了单元格内容中，形如：abcd$aaa#bbb$ccc_KKFD
                            List<String> sqlVarNamesList = IndexConstant.getSqlVarNamesList();
                            String cellStringValue = getCellStringValue(cell);
                            for (String str : sqlVarNamesList) {
                                cellStringValue = cellStringValue.replaceAll(str, "");
                            }
                            cell.setCellType(Cell.CELL_TYPE_STRING);
                            cell.setCellValue(cellStringValue);
                        }
                    } catch (RuntimeException e) {
                    }

                    //计算报表中所有存在的公式
                    if (cell != null && cell.getCellType() == Cell.CELL_TYPE_FORMULA) {
                        try {
                            evaluator.evaluateFormulaCell(cell);
                        } catch (Exception e) {
                            //e.printStackTrace();
                        }
                    }

                    //清理专用批注，批注中首行带有两个 $$ 的
                    try {
                        if (cell.getCellComment() != null &&
                                cell.getCellComment().getString() != null &&
                                !"".equals(cell.getCellComment().getString().getString()) &&
                                cell.getCellComment().getString().getString().startsWith("$$")) {
                            //删除这个专用批注
                            cell.removeCellComment();
                        }
                    } catch (Exception e) {
                    }

                }
            }
        }
    }

    /**
     * 用于将任意格式的单元格内容读取为 String 类型
     *
     * @param cell poi cell 对象
     * @return 取出的 String
     */
    private String getCellStringValue(Cell cell) {
        String cellValue = "";
        switch (cell.getCellType()) {
            case Cell.CELL_TYPE_STRING:
                cellValue = cell.getStringCellValue();
                if (cellValue.trim().equals("") || cellValue.trim().length() <= 0)
                    cellValue = " ";
                break;
            case Cell.CELL_TYPE_NUMERIC:
                cellValue = String.valueOf(cell.getNumericCellValue());
                break;
            case Cell.CELL_TYPE_FORMULA:
                cell.setCellType(Cell.CELL_TYPE_FORMULA);
                cellValue = String.valueOf(cell.getNumericCellValue());
                break;
            case Cell.CELL_TYPE_BLANK:
                cellValue = " ";
                break;
            case Cell.CELL_TYPE_BOOLEAN:
                break;
            case Cell.CELL_TYPE_ERROR:
                break;
            default:
                break;
        }
        return cellValue;
    }

    /**
     * 用于简化的调用获取模板中定义的参变量
     *
     * @return
     * @throws Exception
     */
    public Map<String, String> getDefinedVars(Workbook workbook) throws Exception {
        Map<String, String> varsDefinedMap = parseVarsFromExcelTemplate(workbook);
        return varsDefinedMap;
    }

    /**
     * 根据 $B 所定义的补录区进行着色
     *
     * @param workbook
     */
    public void highlightRewriteRegion(Workbook workbook, short color) throws Exception {

        List<String> reinputRegion = getReinputRegion(workbook);

        for (String posRegion : reinputRegion) {
            //拆解需用量
            Integer sheetIndex = Integer.parseInt(posRegion.split("@")[0].split("-")[0]);
            String positions = posRegion.split("@")[1];// 1:-27,13 或 11,4-38,15
            String[] points = positions.split("-");//0: point1:x,y 1:point2:x,y

            if (points[0] != null && !"".equals(points[0])) {
                int point1X = Integer.parseInt(points[0].split(",")[0]);
                int point1Y = Integer.parseInt(points[0].split(",")[1]);
                int point2X = Integer.parseInt(points[1].split(",")[0]);
                int point2Y = Integer.parseInt(points[1].split(",")[1]);

                //交换坐标
                int temp = 0;
                if (point2X < point1X & point2Y > point1Y) {
                    temp = point2X;
                    point1X = point2X;
                    point2X = temp;
                } else if (point2X < point1X & point2Y < point1Y) {
                    temp = point2X;
                    point1X = point2X;
                    point2X = temp;

                    temp = point2Y;
                    point1Y = point2Y;
                    point2Y = temp;
                } else if (point2X > point1X & point2Y < point1Y) {
                    temp = point2Y;
                    point1Y = point2Y;
                    point2Y = temp;
                } else if (point2X == point1X & point2Y < point1Y) {
                    temp = point2Y;
                    point1Y = point2Y;
                    point2Y = temp;
                } else if (point2X < point1X & point1Y == point2Y) {
                    temp = point2X;
                    point1X = point2X;
                    point2X = temp;
                }

                //遍历为高亮区作色
                if (sheetIndex != null) {
                    Sheet sheet = workbook.getSheetAt(sheetIndex);
                    if (sheet == null) {
                        return;
                    }
                    ////////////  先处理区域型填充
                    for (int i = point1X; i <= point2X; i++) {
                        //列增加
                        Row row = sheet.getRow(i);
                        if (row == null) {
                            row = sheet.createRow(i);
                        }
                        for (int j = point1Y; j <= point2Y; j++) {
                            Cell cell = row.getCell(j);
                            if (cell == null) {
                                cell = row.createCell(j);
                            }
                            CellStyle cellStyle = cell.getCellStyle();
                            if (color == 0) {
                                //TODO 此处应该将模板中对应位置的样式复制过来，而不是直接使用null，这将破坏原有样式
                                cellStyle.setFillPattern(XSSFCellStyle.NO_FILL);
                                cellStyle.setFillForegroundColor(color);
                                cell.setCellStyle(cellStyle);
                            } else {
                                cellStyle = workbook.createCellStyle();
                                cellStyle.setFillPattern(XSSFCellStyle.SOLID_FOREGROUND);
                                cellStyle.setFillForegroundColor(HSSFColor.PALE_BLUE.index);
                                cellStyle.setBorderBottom(CellStyle.BORDER_THIN);
                                cellStyle.setBottomBorderColor(IndexedColors.WHITE.getIndex());
                                cellStyle.setBorderLeft(CellStyle.BORDER_THIN);
                                cellStyle.setLeftBorderColor(IndexedColors.WHITE.getIndex());
                                cellStyle.setBorderRight(CellStyle.BORDER_THIN);
                                cellStyle.setRightBorderColor(IndexedColors.WHITE.getIndex());
                                cellStyle.setBorderTop(CellStyle.BORDER_THIN);
                                cellStyle.setTopBorderColor(IndexedColors.WHITE.getIndex());
                                cell.setCellStyle(cellStyle);
                            }
                        }
                    }
                }
            } else {
                int point2X = Integer.parseInt(points[1].split(",")[0]);
                int point2Y = Integer.parseInt(points[1].split(",")[1]);
                //遍历为高亮区作色
                if (sheetIndex != null) {
                    Sheet sheet = workbook.getSheetAt(sheetIndex);
                    if (sheet == null) {
                        return;
                    }
                    //列增加
                    Row row = sheet.getRow(point2X);
                    if (row == null) {
                        row = sheet.createRow(point2X);
                    }
                    Cell cell = row.getCell(point2Y);
                    if (cell == null) {
                        cell = row.createCell(point2Y);
                    }
                    CellStyle cellStyle = cell.getCellStyle();
                    if (color == 0) {
                        //TODO 此处应该将模板中对应位置的样式复制过来，而不是直接使用null，这将破坏原有样式
                        cellStyle.setFillPattern(XSSFCellStyle.NO_FILL);
                        cellStyle.setFillForegroundColor(color);
                        cell.setCellStyle(cellStyle);
                    } else {
                        //CellStyle defaultStyle = workbook.getCellStyleAt((short) 0);
                        //if (cellStyle.equals(defaultStyle)) {
                        cellStyle = workbook.createCellStyle();
                        //}
                        cellStyle.setFillPattern(XSSFCellStyle.SOLID_FOREGROUND);
                        cellStyle.setFillForegroundColor(HSSFColor.PALE_BLUE.index);
                        cellStyle.setBorderBottom(CellStyle.BORDER_THIN);
                        cellStyle.setBottomBorderColor(IndexedColors.WHITE.getIndex());
                        cellStyle.setBorderLeft(CellStyle.BORDER_THIN);
                        cellStyle.setLeftBorderColor(IndexedColors.WHITE.getIndex());
                        cellStyle.setBorderRight(CellStyle.BORDER_THIN);
                        cellStyle.setRightBorderColor(IndexedColors.WHITE.getIndex());
                        cellStyle.setBorderTop(CellStyle.BORDER_THIN);
                        cellStyle.setTopBorderColor(IndexedColors.WHITE.getIndex());
                        cell.setCellStyle(cellStyle);
                    }
                }
            }
        }
        return;
    }

    /**
     * 根据模板中已经存在的参变量，查询 $B 分别表示补录的开始和结束区
     *
     * @return 补录区间
     */
    public List<String> getReinputRegion(Workbook workbook) throws Exception {

        //定义返回 List
        List posList = new ArrayList();
        List resPosList = new ArrayList();

        /*
          合法性判断用的 map,key=$B / $Bxx value=计数
          $B 允许有多个，$Bxx 只能有两个
         */
        Map<String, Integer> validMap = new HashMap<>();
        Map<String, String> tmpMap = new HashMap<>();
        //0@$B1=4,2
        for (String pos : varsDefPos) {
            String sheetNo = pos.split("=")[0].split("@")[0];
            String sheetName = workbook.getSheetAt(Integer.parseInt(sheetNo)).getSheetName();
            String varName = pos.split("=")[0].split("@")[1];
            String posStr = pos.split("=")[1];
            String defineVar = pos.split("=")[0];

            Integer count = validMap.get(defineVar);
            if (count == null) {
                if ("$B".equals(defineVar.split("@")[1])) {
                    count = 1;
                    validMap.put(defineVar, count);
                }
            } else {
                if ("$B".equals(defineVar.split("@")[1])) {
                    count++;
                    validMap.put(defineVar, count);
                }
            }

            //表示单个 $B 的情形，单个为 $B 表示单个允许输入的单元格
            if ("$B".equals(defineVar.split("@")[1])) {
                resPosList.add(sheetNo + "-" + sheetName + "@-" + posStr);
            }

            //表示 $Bxx 的情况，表示多个 $Bxx 形成的区域
            else if (defineVar.split("@")[1].contains("$B") && defineVar.split("@")[1].length() > 2) {
                if (count == null) {
                    //表示第一次写入到 validMap
                    validMap.put(defineVar, 1);
                    tmpMap.put(defineVar, posStr);
                } else if (count == 1) {
                    validMap.put(defineVar, ++count);
                    //如果是第二次写入，则获取该 varName 的第一次的值
                    String firstPosStr = tmpMap.get(defineVar);
                    posList.add(sheetNo + "-" + sheetName + "@" + firstPosStr + "-" + posStr);
                } else {
                    validMap.put(defineVar, ++count);
                    continue;
                }
            }
        }

        //开始进行合法性判断
        /*
            $B：允许一个都没有，或任意多个
            $Bxx 只允许有两个，当为一个或多余两个时报错
         */
        //$Bxx 个数配对时的异常处理
        Set<String> keySet = validMap.keySet();
        for (String defineVar : keySet) {
            Integer count = validMap.get(defineVar);

            //第一个条件是 Excel 单格补录区允许有任意多个，第二个条件是 $Bxx 必须只能是配对的两个
            if (!defineVar.split("@")[1].equals("$B") && count != null && (count <= 1 | count > 2)) {
                int sheetNo = Integer.parseInt(defineVar.split("@")[0]);
                throw new Exception(workbook.getSheetName(sheetNo) + "页的参变量" + defineVar.split("@")[1] + " 定义了" + count + "个，只能是2个");
            }
        }
        //校正矩形坐标 0-Sheet1@9,5-27,13
        for (int i = 0; i < posList.size(); i++) {
            String pos = (String) posList.get(i);
            String sheetNo = pos.split("@")[0];
            String[] points = pos.split("@")[1].split("-");
            int point1X = Integer.parseInt(points[0].split(",")[0]);
            int point1Y = Integer.parseInt(points[0].split(",")[1]);
            int point2X = Integer.parseInt(points[1].split(",")[0]);
            int point2Y = Integer.parseInt(points[1].split(",")[1]);
            //交换坐标
            if (point1X == point2X && point1Y == point2Y) {
                resPosList.add(sheetNo + "@-" + point1X + "," + point1Y);
            } else if (point2X > point1X && point2Y > point1Y) {
                resPosList.add(sheetNo + "@" + point1X + "," + point1Y + "-" + point2X + "," + point2Y);
            } else if (point1X > point2X && point1Y > point2Y) {
                resPosList.add(sheetNo + "@" + point2X + "," + point2Y + "-" + point1X + "," + point1Y);
            } else if (point1X > point2X && point1Y < point2Y) {
                resPosList.add(sheetNo + "@" + point2X + "," + point1Y + "-" + point1X + "," + point2Y);
            } else if (point2X > point1X && point2Y < point1Y) {
                resPosList.add(sheetNo + "@" + point1X + "," + point2Y + "-" + point2X + "," + point1Y);
            } else if (point1X == point2X && point1Y < point2Y) {
                resPosList.add(sheetNo + "@" + point1X + "," + point1Y + "-" + point2X + "," + point2Y);
            } else if (point1X == point2X && point2Y < point1Y) {
                resPosList.add(sheetNo + "@" + point2X + "," + point2Y + "-" + point1X + "," + point1Y);
            } else if (point1Y == point2Y && point1X < point2X) {
                resPosList.add(sheetNo + "@" + point1X + "," + point1Y + "-" + point2X + "," + point2Y);
            } else if (point1Y == point2Y && point1Y > point2Y) {
                resPosList.add(sheetNo + "@" + point2X + "," + point2Y + "-" + point1X + "," + point1Y);
            }
        }
        return resPosList;
    }

    /**
     * 将补录模板中的文字复制到技术模板中的对应区域中，且仅复制以 $B 及 $Bxx 所定义的区域中的文字内容
     *
     * @param rewriteFile 补录模板路径
     */
    public void rewriteAreaCopy2Temp(String rewriteFile) throws Exception {
        //打开补录模板
        Workbook workbookRewrite = ExcelUtil.openOtherExcel(rewriteFile);
        List<String> reinputRegion = getReinputRegion(workbook);
        for (String posRegion : reinputRegion) {
            //拆解需用量
            int sheetIndex = Integer.parseInt(posRegion.split("@")[0].split("-")[0]);
            String positions = posRegion.split("@")[1];// 1:-27,13 或 11,4-38,15
            String[] points = positions.split("-");//0: point1:x,y 1:point2:x,y

            Sheet sheet4No = workbook.getSheetAt(sheetIndex); //技术模板
            Sheet sheet8No = workbookRewrite.getSheetAt(sheetIndex); //补录模板
            //处理矩形区域
            if (points[0] != null && !"".equals(points[0])) {
                int point1X = Integer.parseInt(points[0].split(",")[0]);
                int point1Y = Integer.parseInt(points[0].split(",")[1]);
                int point2X = Integer.parseInt(points[1].split(",")[0]);
                int point2Y = Integer.parseInt(points[1].split(",")[1]);

                for (int i = point1X; i <= point2X; i++) {
                    Row row4 = sheet4No.getRow(i);
                    Row row8 = sheet8No.getRow(i);
                    if (row4 == null) {
                        row4 = sheet4No.createRow(i);
                    }
                    if (row8 == null) continue;
                    //补录模板的参数
                    for (int j = point1Y; j <= point2Y; j++) {
                        Cell cell4 = row4.getCell(j);
                        Cell cell8 = row8.getCell(j);
                        if (cell8 == null) {
                            continue;
                        }
                        if (cell4 == null) {
                            cell4 = row4.createCell(j);
                        }
                        cell4.setCellType(cell8.getCellType());
                        int cellType = cell8.getCellType();
                        switch (cellType) {
                            case Cell.CELL_TYPE_STRING:
                                String cellValue = cell8.getStringCellValue();
                                if (cellValue.trim().equals("") || cellValue.trim().length() <= 0) {
                                    cell4.setCellValue(" ");
                                } else {
                                    cell4.setCellValue(cellValue);
                                }
                                break;
                            case Cell.CELL_TYPE_NUMERIC:
                            case Cell.CELL_TYPE_FORMULA:
                                cell4.setCellValue(cell8.getNumericCellValue());
                                break;
                            case Cell.CELL_TYPE_BLANK:
                                cell4.setCellValue(" ");
                                break;
                            case Cell.CELL_TYPE_BOOLEAN:
                                break;
                            case Cell.CELL_TYPE_ERROR:
                                break;
                            default:
                                break;
                        }
                    }
                }
            } else { //处理单格
                int point2X = Integer.parseInt(points[1].split(",")[0]);
                int point2Y = Integer.parseInt(points[1].split(",")[1]);

                Row row8 = sheet8No.getRow(point2X);
                Row row4 = sheet4No.getRow(point2X);
                if (row8 == null) {
                    continue;
                }
                Cell cell4 = row4.getCell(point2Y);
                Cell cell8 = row8.getCell(point2Y);
                if (cell8 == null) {
                    continue;
                }
                if (cell4 == null) {
                    cell4 = row4.createCell(point2Y);
                }
                cell4.setCellType(cell8.getCellType());
                int cellType = cell8.getCellType();
                switch (cellType) {
                    case Cell.CELL_TYPE_STRING:
                        String cellValue = cell8.getStringCellValue();
                        if (cellValue.trim().equals("") || cellValue.trim().length() <= 0) {
                            cell4.setCellValue(" ");
                        } else {
                            cell4.setCellValue(cellValue);
                        }
                        break;
                    case Cell.CELL_TYPE_NUMERIC:
                    case Cell.CELL_TYPE_FORMULA:
                        cell4.setCellValue(cell8.getNumericCellValue());
                        break;
                    case Cell.CELL_TYPE_BLANK:
                        cell4.setCellValue(" ");
                        break;
                    case Cell.CELL_TYPE_BOOLEAN:
                        break;
                    case Cell.CELL_TYPE_ERROR:
                        break;
                    default:
                        break;
                }
            }
        }
    }

    public boolean beContainsReWriteRegion() {

        if (varsDefPos.size() == 0) {
            return false;
        }

        //varsDefPos: [0@_multi_=2,2, 0@$S1=7,2, 0@$B=10,2, 0@$B=12,2, 0@$E1=20,2]
        for (int i = 0; i < varsDefPos.size(); i++) {
            String oriStr = varsDefPos.get(i);
            if (oriStr.contains("$B")) {
                return true;
            }
        }
        return false;
    }

    public Workbook getWorkbook() {
        return workbook;
    }


}
